#include "AdbClient.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include "zipfile.h"
#include <sys/types.h>
#include <sys/stat.h>
#include "sysdeps.h"
#include "adb.h"
#include "stringcov.h"
#include <assert.h>
#include "AdbUtils.h"
#include <sstream>


#define  TRACE_TAG  TRACE_ADB

#undef close
#undef read
#undef write

int adb_send_emulator_command(int  argc, char**  argv);

void AdbClient::initLib()
{
	adb_sysdeps_init();
	adb_trace_init();
}


AdbClient::AdbClient()
{
	m_adb_transport = kTransportAny;
	m_adb_server_port = DEFAULT_ADB_PORT;
	memset(m_adb_error, 0, sizeof(m_adb_error));
}

AdbClient::~AdbClient()
{


}


int AdbClient::start_server()
{
	int fd = connect("host:start-server");
	if (fd > 0)
	{
		adb_close(fd);
		return 0;
	}
	else
		return -1;
}

int AdbClient::kill_server()
{
	int fd = _connect("host:kill");
	if (fd > 0)
	{
		adb_close(fd);
		return 0;
	}
	else
		return -1;
}




void AdbClient::set_transport(transport_type type, const char* serial)
{
    m_adb_transport = type;
    m_adb_serial = serial;
}

void AdbClient::set_tcp_specifics(int server_port)
{
    m_adb_server_port = server_port;
}

void AdbClient::set_tcp_name(const char* hostname)
{
    m_adb_server_name = hostname;
}

int  AdbClient::get_emulator_console_port(void)
{
    const char*   serial = m_adb_serial.c_str();
    int           port;

    if (serial == NULL) {
        /* if no specific device was specified, we need to look at */
        /* the list of connected devices, and extract an emulator  */
        /* name from it. two emulators is an error                 */
        char*  tmp = query("host:devices");
        char*  p   = tmp;
        if(!tmp) {
            printf("no emulator connected\n");
            return -1;
        }
        while (*p) {
            char*  q = strchr(p, '\n');
            if (q != NULL)
                *q++ = 0;
            else
                q = p + strlen(p);

            if (!memcmp(p, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1)) {
                if (serial != NULL) {  /* more than one emulator listed */
                    free(tmp);
                    return -2;
                }
                serial = p;
            }

            p = q;
        }
        free(tmp);

        if (serial == NULL)
            return -1;  /* no emulator found */
    }
    else {
        if (memcmp(serial, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1) != 0)
            return -1;  /* not an emulator */
    }

    serial += sizeof(LOCAL_CLIENT_PREFIX)-1;
    port    = strtol(serial, NULL, 10);
    return port;
}



const char *AdbClient::error(void)
{
    return m_adb_error;
}

int AdbClient::switch_socket_transport(int fd)
{
    char service[64];
    char tmp[5];
    int len;

    if (!m_adb_serial.empty())
        snprintf(service, sizeof service, "host:transport:%s", m_adb_serial.c_str());
    else {
        char* transport_type = "???";

         switch (m_adb_transport) {
            case kTransportUsb:
                transport_type = "transport-usb";
                break;
            case kTransportLocal:
                transport_type = "transport-local";
                break;
            case kTransportAny:
                transport_type = "transport-any";
                break;
            case kTransportHost:
                // no switch necessary
                return 0;
                break;
        }

        snprintf(service, sizeof service, "host:%s", transport_type);
    }
    len = strlen(service);
    snprintf(tmp, sizeof tmp, "%04x", len);

    if(writex(fd, tmp, 4) || writex(fd, service, len)) {
        strcpy(m_adb_error, "write failure during connection");
        adb_close(fd);
        return -1;
    }
    D("Switch transport in progress\n");

    if(status(fd)) {
        adb_close(fd);
        D("Switch transport failed\n");
        return -1;
    }
    D("Switch transport success\n");
    return 0;
}

int AdbClient::status(int fd)
{
    unsigned char buf[5];
    unsigned len;

    if(readx(fd, (char*)buf, 4)) {
        strcpy(m_adb_error, "protocol fault (no status)");
        return -1;
    }

    if(!memcmp(buf, "OKAY", 4)) {
        return 0;
    }

    if(memcmp(buf, "FAIL", 4)) {
        sprintf(m_adb_error,
                "protocol fault (status %02x %02x %02x %02x?!)",
                buf[0], buf[1], buf[2], buf[3]);
        return -1;
    }

    if(readx(fd, (char*)buf, 4)) {
        strcpy(m_adb_error, "protocol fault (status len)");
        return -1;
    }
    buf[4] = 0;
    len = strtoul((char*)buf, 0, 16);
    if(len > 255) len = 255;
    if(readx(fd, m_adb_error, len)) {
        strcpy(m_adb_error, "protocol fault (status read)");
        return -1;
    }
    m_adb_error[len] = 0;
    return -1;
}

int AdbClient::_connect(const char *service)
{
    char tmp[5];
    int len;
    int fd;

    D("_adb_connect: %s\n", service);
    len = strlen(service);
    if((len < 1) || (len > 1024)) {
        strcpy(m_adb_error, "service name too long");
        return -1;
    }
    snprintf(tmp, sizeof tmp, "%04x", len);

    if (!m_adb_server_name.empty())
        fd = socket_network_client(m_adb_server_name.c_str(), m_adb_server_port, SOCK_STREAM);
    else
        fd = socket_loopback_client(m_adb_server_port, SOCK_STREAM);

    if(fd < 0) {
        strcpy(m_adb_error, "cannot connect to daemon");
        return -2;
    }

    if (memcmp(service,"host",4) != 0 && switch_socket_transport(fd)) {
        return -1;
    }

    if(writex(fd, tmp, 4) || writex(fd, service, len)) {
        strcpy(m_adb_error, "write failure during connection");
        adb_close(fd);
        return -1;
    }

    if(status(fd)) {
        adb_close(fd);
        return -1;
    }

    D("_adb_connect: return fd %d\n", fd);
    return fd;
}

int AdbClient::connect(const char *service)
{
    // first query the adb server's version
    int fd = _connect("host:version");

    D("adb_connect: service %s\n", service);
    if(fd == -2 && !m_adb_server_name.empty()) {
        fprintf(stderr,"** Cannot start server on remote host\n");
        return fd;
    } else if(fd == -2) {
        fprintf(stdout,"* daemon not running. starting it now on port %d *\n",
                m_adb_server_port);
    start_server:
        if(launch_server(m_adb_server_port)) {
            fprintf(stderr,"* failed to start daemon *\n");
            return -1;
        } else {
            fprintf(stdout,"* daemon started successfully *\n");
        }
        /* give the server some time to start properly and detect devices */
        adb_sleep_ms(3000);
        // fall through to _adb_connect
    } else {
        // if server was running, check its version to make sure it is not out of date
        char buf[100];
        size_t n;
        int version = ADB_SERVER_VERSION - 1;

        // if we have a file descriptor, then parse version result
        if(fd >= 0) {
            if(readx(fd, buf, 4)) goto error;

            buf[4] = 0;
            n = strtoul(buf, 0, 16);
            if(n > sizeof(buf)) goto error;
            if(readx(fd, buf, n)) goto error;
            adb_close(fd);

            if (sscanf(buf, "%04x", &version) != 1) goto error;
        } else {
            // if fd is -1, then check for "unknown host service",
            // which would indicate a version of adb that does not support the version command
            if (strcmp(m_adb_error, "unknown host service") != 0)
                return fd;
        }

        if(version != ADB_SERVER_VERSION) {
            printf("adb server is out of date.  killing...\n");
            fd = _connect("host:kill");
            adb_close(fd);

            /* XXX can we better detect its death? */
            adb_sleep_ms(2000);
            goto start_server;
        }
    }

    // if the command is start-server, we are done.
    if (!strcmp(service, "host:start-server"))
        return 0;

    fd = _connect(service);
    if(fd == -1) {
        fprintf(stderr,"error: %s\n", m_adb_error);
    } else if(fd == -2) {
        fprintf(stderr,"** daemon still not running\n");
    }
    D("adb_connect: return fd %d\n", fd);

    return fd;
error:
    adb_close(fd);
    return -1;
}

int AdbClient::read(int fd, char* data, int len)
{
	return adb_read(fd, data, len);
}

int AdbClient::write(int fd, const char* data, int len)
{
	return adb_write(fd, data, len);
}

int AdbClient::readx(int fd, char* data, int len)
{
	return ::readx(fd, data, len);
}

int AdbClient::writex(int fd, const char* data, int len)
{
	return ::writex(fd, data, len) ;
}

int AdbClient::close(int fd)
{
	return adb_close(fd);
}



int AdbClient::command(const char *service)
{
    int fd = connect(service);
    if(fd < 0) {
        return -1;
    }

	int rslt = status(fd);
    adb_close(fd);
    return rslt;
}

static void read_and_dump(int fd,shell_exec_callback callback, void* ptr)
{
	char buf[4096] = { 0 };
	int len;

	while (fd >= 0) {
		D("read_and_dump(): pre adb_read(fd=%d)\n", fd);
		len = adb_read(fd, buf, 4096);
		D("read_and_dump(): post adb_read(fd=%d): len=%d\n", fd, len);
		if (len == 0) {
			break;
		}

		if (len < 0) {
			if (errno == EINTR) continue;
			break;
		}
		
		if (callback(buf, len, ptr) != 0)
			break;
	}
}

int AdbClient::shell_exec(const char *service,shell_exec_callback callback, void* ptr)
{
	int fd = connect(service);

	if (fd >= 0) {
		D("about to read_and_dump(fd=%d)\n", fd);
		read_and_dump(fd,callback,ptr);
		D("read_and_dump() done.\n");
		adb_close(fd);
		return 0;
	}
	else {
		fprintf(stderr, "error: %s\n", error());
		return -1;
	}
}

int _shell_callback(const char* data, int len, void* ptr)
{
	std::string* str = (std::string*)ptr;
	if(str)
		str->append(data,len);
	return 0;
}

int AdbClient::shell_exec(const char *service, std::string* out)
{
	return shell_exec(service, _shell_callback, out);
}


char *AdbClient::query(const char *service)
{
	char buf[5];
	unsigned n;
	char *tmp;

	D("adb_query: %s\n", service);
	int fd = connect(service);
	if (fd < 0) {
		fprintf(stderr, "error: %s\n", m_adb_error);
		return 0;
	}

	if (readx(fd, buf, 4)) goto oops;

	buf[4] = 0;
	n = strtoul(buf, 0, 16);
	if (n >= 0xffff) {
		strcpy(m_adb_error, "reply is too long (>= 64kB)");
		goto oops;
	}

	tmp = (char *)malloc(n + 1);
	if (tmp == 0) goto oops;

	if (readx(fd, tmp, n) == 0) {
		tmp[n] = 0;
		adb_close(fd);
		return tmp;
	}
	free(tmp);

oops:
	adb_close(fd);
	return 0;
}


int AdbClient::send_emulator_command(int  argc, char**  argv)
{
	return adb_send_emulator_command(argc,argv);
}

//\r\nתΪ\n
int _capture_callback(const char* data, int len, void* ptr)
{
	char buf[4096];

	assert(len<=sizeof(buf));
	int pos = 0;
	for (int i = 0; i < len - 1; i++)
	{
		if (data[i] == '\r' && data[i + 1] == '\n')
		{

			buf[pos++] = '\n';
			++i;
		}
		else
			buf[pos++] = data[i];
	}

	if (data[len - 1] != '\r')
		buf[pos++] = data[len - 1];
	else
	{
		int j = 0;
	}
	fwrite(buf, pos, 1, (FILE*)ptr);
	return 0;
}

bool AdbClient::capture(const char* filePath)
{
	std::wstring path;
	UTF8_to_Unicode(filePath, strlen(filePath),path);
	FILE* file = _wfopen(path.c_str(), L"wb");
	if (file)
	{
		int rslt=shell_exec("shell:screencap -p", _capture_callback, file);
		fclose(file);
		return rslt==0;
	}
	else
		return false;
}


std::string AdbClient::get_abi()
{
	std::string rslt;
	if(shell_exec("shell:getprop ro.product.cpu.abi",&rslt)!=0)
		return rslt;

	size_t find = rslt.rfind('\r');
	if (find != rslt.npos)
		return rslt.substr(0, find);
	else
		return rslt;
}

std::string AdbClient::get_sdk_version()
{
	std::string rslt;
	if (shell_exec("shell:getprop ro.build.version.sdk", &rslt) != 0)
		return rslt;

	size_t find = rslt.rfind('\r');
	if (find != rslt.npos)
		return rslt.substr(0, find);
	else
		return rslt;
}

bool AdbClient::run_mini_cap(const char* commend, shell_exec_callback callback, void* ptr)
{
	std::string abi = get_abi();
	std::string sdkVar = get_sdk_version();
	int sdk = atoi(sdkVar.c_str());
	 
	if (abi.empty() || sdk==0)
		return false;

	std::string cmd;
	if (sdk >= 16)
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -h";
	else
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap-nopie -h";
	std::string rslt;
	if (shell_exec(cmd.c_str(), &rslt) != 0)
		return false;

	//检查是否安装了minicap
	if (rslt.substr(0,5) == "Usage")
	{
		if (sdk >= 16)
			cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap ";
		else
			cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap-nopie ";
		cmd.append(commend);
		return shell_exec(cmd.c_str(), callback,ptr) == 0;
	}


	//上传minicap
	std::string path= get_current_path() + "minicap\\bin\\";
	path.append(abi);
	path.append("\\");

	std::string libpath = get_current_path() + "minicap\\libs\\android-";
	libpath.append(sdkVar+"\\");
	libpath.append(abi);
	libpath.append("\\");

	std::string file = libpath + "minicap.so";
	if (push(file.c_str(), "/data/local/tmp", NULL, NULL) != 0)
		return false;

	if (sdk >= 16)
		file = path + "minicap";
	else
		file = path + "minicap-nopie";

	if (push(file.c_str(), "/data/local/tmp", NULL, NULL) != 0)
		return false;


	if (sdk >= 16)
	{
		shell_exec("shell:chmod 777 /data/local/tmp/minicap", NULL);
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap ";
	}
	else
	{
		shell_exec("shell:chmod 777 /data/local/tmp/minicap-nopie", NULL);
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap-nopie ";
	}
		
	cmd.append(commend);
	return shell_exec(cmd.c_str(), callback,ptr)==0;
}



bool AdbClient::run_mini_touch(shell_exec_callback callback, void* ptr)
{
	std::string abi = get_abi();
	std::string sdkVar = get_sdk_version();
	int sdk = atoi(sdkVar.c_str());

	if (abi.empty() || sdk == 0)
		return false;

	std::string cmd;
	if (sdk >= 16)
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minitouch -h";
	else
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minitouch-nopie -h";
	std::string rslt;
	if (shell_exec(cmd.c_str(), &rslt) != 0)
		return false;

	//检查是否安装了minitouch
	//if (rslt.substr(0,5) == "Usage")
	//{
	//	if (sdk >= 16)
	//		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minitouch";
	//	else
	//		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minitouch-nopie ";
	//	return shell_exec(cmd.c_str(), callback, ptr) == 0;
	//}


	//上传minicap
	std::string file;
	std::string path = get_current_path() + "minitouch\\";
	path.append(abi);
	path.append("\\");

	if (sdk >= 16)
		file = path + "minitouch";
	else
		file = path + "minitouch-nopie";

	if (push(file.c_str(), "/data/local/tmp", NULL, NULL) != 0)
		return false;


	if (sdk >= 16)
	{
		shell_exec("shell:chmod 777 /data/local/tmp/minitouch", NULL);
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minitouch";
	}
	else
	{
		shell_exec("shell:chmod 777 /data/local/tmp/minitouch-nopie", NULL);
		cmd = "shell:LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minitouch-nopie";
	}

	return shell_exec(cmd.c_str(), callback, ptr) == 0;

}

int AdbClient::is_phone_locked(bool* isScreenOff, bool* isLock)
{
	std::string rslt;
	int rtn= shell_exec("shell:dumpsys window policy", &rslt);
	if (rtn != 0)
		return rtn;

	if (isLock)
	{
		//判断是否需要滑动解锁，显示了锁定界面需要解锁
		if (rslt.find("mShowingLockscreen=false") != rslt.npos)
			*isLock = false;
		else
			*isLock = true;
	}

	if (isScreenOff)
	{
		if (rslt.find("mScreenOnFully=false") != rslt.npos)
			*isScreenOff = true;
		else
			*isScreenOff = false;
	}
	return 0;
}

int AdbClient::unlock_phone()
{
	bool isScreenOff, isLock;
	int rtn = is_phone_locked(&isScreenOff,&isLock);//判断手机是否锁定
	if (rtn != 0)
		return rtn;

	if (isScreenOff)
	{
		//点击电源键
		rtn = shell_exec("shell:input keyevent 26", NULL);
		if (rtn != 0)
			return rtn;
	}

	if (isLock)
	{
		//rtn = shell_exec("shell:input swipe 500 50 500 700", NULL);
		rtn = shell_exec("shell:input swipe 500 700 500 50", NULL);
		if (rtn != 0)
			return rtn;
	}
	return 0;
}

int AdbClient::forward(const char* local, const char* remote)
{
	//forward:tcp:1717;localabstract:minicap
	std::string rslt;
	char buf[1024];

	if(m_adb_serial.empty())
		sprintf(buf,"host:forward:%s;%s", local, remote);
	else
		sprintf(buf, "host-serial:%s:forward:%s;%s", m_adb_serial.c_str(), local, remote);
	if (command(buf) == 0)
	{
		return 0;
	}
	else
		return -1;
}


int AdbClient::get_screen_size(int* w, int* h)
{
	std::string data;
	if (shell_exec("shell:wm size", &data) == 0)
	{
		string key, value;
		data = data.substr(data.find("Physical size:"));
		ParserKeyValue(data.c_str(),data.length(), key, value, ':');
		if (key == "Physical size")
		{
			string value1, value2;
			ParserKeyValue(value.c_str(), value.length(), value1, value2, 'x');
			*w = atoi(value1.c_str());
			*h = atoi(value2.c_str());
			return 0;
		}
	}
	return -1;
}

int AdbClient::device_list(std::vector<std::string>* vector)
{
	char* devices = query("host:devices");
	if (devices)
	{
		std::stringstream stream;
		stream.write(devices,strlen(devices));
		std::string line;

		while (std::getline(stream, line))
		{
			auto find = line.find('\t');
			if (find != string::npos)
			{
				auto name = line.substr(0, find);
				vector->push_back(name);
			}
		}

		free(devices);
		return 0;
	}
	return -1;
}


static int check_file(const char* filename)
{
	struct _stat64i32 st;

	if (filename == NULL) {
		return 0;
	}
	std::wstring str;
	UTF8_to_Unicode(filename, strlen(filename), str);
	if (_wstat(str.c_str(), &st) != 0) {
		fprintf(stderr, "can't find '%s' to install\n", str.c_str());
		return 1;
	}

	if (!S_ISREG(st.st_mode)) {
		fprintf(stderr, "can't install '%s' because it's not a file\n", str.c_str());
		return 1;
	}
	return 0;
}

static const char* get_basename(const char* filename)
{
	const char* basename = adb_dirstop(filename);
	if (basename) {
		basename++;
		return basename;
	}
	else {
		return filename;
	}
}

static char *dupAndQuote(const char *s)
{
	const char *ts;
	size_t alloc_len;
	char *ret;
	char *dest;

	ts = s;

	alloc_len = 0;

	for (; *ts != '\0'; ts++) {
		alloc_len++;
		if (*ts == ' ' || *ts == '"' || *ts == '\\' || *ts == '(' || *ts == ')') {
			alloc_len++;
		}
	}

	ret = (char *)malloc(alloc_len + 1);

	ts = s;
	dest = ret;

	for (; *ts != '\0'; ts++) {
		if (*ts == ' ' || *ts == '"' || *ts == '\\' || *ts == '(' || *ts == ')') {
			*dest++ = '\\';
		}

		*dest++ = *ts;
	}

	*dest++ = '\0';

	return ret;
}

int AdbClient::delete_file(char* filename)
{
	char buf[4096];
	char* quoted;

	snprintf(buf, sizeof(buf), "shell:rm ");
	quoted = dupAndQuote(filename);
	strncat(buf, quoted, sizeof(buf) - 1);
	free(quoted);
	return shell_exec(buf,NULL);
}

int AdbClient::install(const char* apk_file, process_callback callback, void* ptr)
{
	int err;
	static const char *const DATA_DEST = "/data/local/tmp/%s";
	const char* where = DATA_DEST;
	if (check_file(apk_file)) {
		return 1;
	}

	char apk_dest[PATH_MAX];
	snprintf(apk_dest, sizeof apk_dest, where, get_basename(apk_file));
	err = push(apk_file, apk_dest, callback, ptr);

	if(err!=0)
		return err;

	char cmd[1024];
	snprintf(cmd, sizeof cmd, "shell:pm install %s", apk_dest);
	return shell_exec(cmd,NULL);
}

int AdbClient::uninstall(const char* pkgName)
{
	//pm uninstall
	char cmd[1024];
	snprintf(cmd, sizeof cmd, "shell:pm uninstall %s", pkgName);
	return shell_exec(cmd, NULL);
}



